<?php

namespace Jin\Laraveltools\Console;

use Closure;
use Str;

class ApiCommand extends ColorCommand
{
    protected $signature = 'j:api';

    protected $description = '生成api文档';

    protected $TAG;
    protected $TAG_GROUP;
    protected $TAG_RESPONSE;
    protected $TAG_END;
    protected $TAG_HIDE;
    protected $TAG_PARAM;
    protected $V_OUT_FILE;
    protected $V_ON = false;
    protected $API_OUT_FILE;

    protected $help = "";
    protected $classMap = [];

    protected $docs = [];

    public function handle()
    {
        $config = config("j-tools");

        $this->TAG_HIDE = $config["hideTag"];
        $this->TAG_GROUP = $config["groupTag"];
        $this->TAG_RESPONSE = $config["responseTag"];
        $this->TAG_PARAM = $config["paramTag"];
        $this->TAG = $config["tag"];
        $this->TAG_END = $config["endTag"];
        $this->V_OUT_FILE = $config["validation"]["file"];
        $this->V_ON = $config["validation"]["on"];
        $this->API_OUT_FILE = $config["outFile"];

        //获取基本数据
        $this->docs = $this->getDocs();
        $this->format();

        //生成验证文件
        if ($this->V_ON) {
            $this->verifyConfig();
        }

        //生成api文档
        $this->createApiFile();

    }

    protected function verifyConfig()
    {
        $vArr = [];

        foreach ($this->docs as $doc) {
            $vs = [];
            foreach ($doc["info"]["params"] ?? [] as $param) {
                

                $v = $param["v"] ?? null;

                if (!$v) {
                    continue;
                }

                $info = [];
                $info["name"] = $param["name"];
                $info["v"] = $v;
                $vs[] = $info;

            }

            if ($vs) {
                $vArr[$doc["controller"]] = $vs;
            }
        }

        $outStr = json_encode($vArr);

        file_put_contents($this->V_OUT_FILE, $outStr);

    }

    protected function getDocs()
    {
        $docs = [];
        foreach ($this->laravel->router->getRoutes() as $r) {
            $uri = $r->uri();
            $action = $r->getAction();
            $methods = $r->methods();

            if ($action["uses"] instanceof Closure) {
                continue;
            }
            $arr = explode("@", $action["controller"]);
            if (count($arr) < 2) {
                continue;
            }
            $c = $arr[0];
            $f = $arr[1];
            $doc = [];
            $docr = $this->api($c, $f);
            if (!$docr) {
                $this->error(sprintf("%s@%s error",
                    $c,
                    $f
                ));
                continue;
            }
            $doc["list"] = $docr;
            $doc["methods"] = $methods;
            $doc["controller"] = $action["controller"];
            $this->green(sprintf("%s@%s ok",
                $c,
                $f
            ));
            $docs[] = $doc;
        }
        return $docs;
    }

    protected function format()
    {

        foreach ($this->docs as &$doc) {
            $info = [];
            $paramArr = [];
            foreach ($doc["list"] as &$item) {
                $item["params"] = [];
                $item["body"] = $item["body"]; //这里别加trim,参数body有可能没有header

                switch ($item["tag"]) {
                    case $this->TAG_PARAM:
                        $body = $item["body"];
                        $bodyArr = explode("\n", $body);

                        $header = array_shift($bodyArr);
                        $headerArr = explode(" ", $header);
                        $param = [];
                        $param["name"] = $item["key"];
                        $param["type"] = array_shift($headerArr);
                        $param["describe"] = array_shift($headerArr);

                        foreach ($bodyArr as $line) {
                            $line = trim($line);
                            if (Str::startsWith($line, '-v')) {
                                $v = Str::substr($line, 2);
                                $param["v"] = trim($v);
                            }

                        }
                        $paramArr[] = $param;
                        break;
                    case $this->TAG:

                        $info["name"] = $item["key"];
                        $info["describe"] = $item["body"];
                        break;
                    case $this->TAG_RESPONSE:
                        $info["response"] = $item["body"];
                        break;
                }
            }
            if ($paramArr) {
                $info["params"] = $paramArr;
            }
            $info["methods"] = join(",", $doc["methods"]);
            $doc["info"] = $info;
        }
    }

    protected function api($c, $f)
    {
        $r = $this->classMap[$c] ?? null;
        $reflector = $r["ref"] ?? null;
        $hide = $r["hide"] ?? false;
        if ($reflector == null) {
            try {
                $reflector = new \ReflectionClass($c);
                $cdoc = $reflector->getDocComment();
                $fileLines = explode("\n", $cdoc);
                $fileR = [
                    "hide" => false,
                    "group" => "",
                ];
                foreach ($fileLines as $line) {
                    $line = trim($line, "* ");
                    if (!Str::startsWith($line, '@')) {
                        continue;
                    }
                    if (Str::startsWith($line, $this->TAG_HIDE)) {
                        $fileR["hide"] = true;
                    }
                }

                $m = [
                    "ref" => $reflector,
                    "hide" => $fileR["hide"],
                    "group" => "",
                ];
                $this->classMap[$c] = $m;
            } catch (\ReflectionException $e) {
                return;
            }
        }

        if ($this->classMap[$c]["hide"]) {
            $this->yellow(sprintf("%s@%s :: %s 标记为隐藏", $c, $f, $c));
            return;
        }

        if ($reflector->hasMethod($f)) {
            $ref = $reflector->getMethod($f);
            $fdoc = $ref->getDocComment();
            $doc = $this->funDoc($fdoc);
            if (!$doc) {
                return;
            }
            //$doc["group"] = $doc["doc_group"] ?? $r["group"] ?? "";
            return $doc;
        }
        return;

    }

    protected function funDoc($str)
    {
        $doc = [];
        $lines = explode("\n", $str);
        $tag = "";
        $key = "";
        $body = "";
        foreach ($lines as $line) {
            $line = trim($line, "* /");
            if (Str::startsWith($line, $this->TAG)) {

                if ($tag) {

                    $doc[] = $this->makeItem($tag, $key, $body);

                    $tag = "";
                    $key = "";
                    $body = "";
                }

                $sArr = explode(" ", $line);

                $tag = array_shift($sArr);

                if (count($sArr) > 0) {
                    $key = array_shift($sArr);
                }

                if (count($sArr) > 0) {

                    $body = join(" ", $sArr);

                }

                if ($tag == $this->TAG_END) {
                    break;
                }

            } else {
                $body = $body . "\n" . $line;
            }
        }

        $doc[] = $this->makeItem($tag, $key, $body);

        return $doc;
    }

    protected function makeItem($tag, $key, $body)
    {
        $item = [];
        $item["tag"] = $tag;
        $item["key"] = $key;
        $item["body"] = $body;
        return $item;
    }

    protected function createApiFile()
    {
        $r = [];
        foreach ($this->docs as $doc) {
            $r[] = $doc["info"];

        }

        // exit();
        $s = addslashes(json_encode($r));

        $tmp = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . "apidoc.blade.php";

        $v = view()->file($tmp, [
            "apidocs" => $s,
        ])->render();

        file_put_contents($this->API_OUT_FILE, $v);

    }

}
